# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Author: Alberto Solino (@agsolino)
#
# Description:
#   [MS-DCOM] Interface implementation
#
#   Best way to learn how to use these calls is to grab the protocol standard
#   so you understand what the call does, and then read the test case located
#   at https://github.com/SecureAuthCorp/impacket/tree/master/tests/SMB_RPC
#
#   Some calls have helper functions, which makes it even easier to use.
#   They are located at the end of this file.
#   Helper functions start with "h"<name of the call>.
#   There are test cases for them too.
#
# ToDo:
# [X] Use the same DCE connection for all the calls. Right now is connecting to the remote machine
#     for each call, making it slower.
#
# [X] Implement a ping mechanism, otherwise the garbage collector at the server shuts down the objects if
#    not used, returning RPC_E_DISCONNECTED
#
from __future__ import division, print_function

import socket
from struct import pack
from threading import Timer, currentThread

from impacket import LOG, hresult_errors
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.dtypes import (
    DWORD,
    GUID,
    HRESULT,
    LONG,
    LPLONG,
    LPWSTR,
    NULL,
    PGUID,
    ULONG,
    ULONGLONG,
    USHORT,
    UUID,
    WIDESTR,
    WSTR,
)
from impacket.dcerpc.v5.ndr import (
    NDRCALL,
    NDRPOINTER,
    NDRSTRUCT,
    NDRTLSTRUCT,
    UNKNOWNDATA,
    NDRUniConformantArray,
)
from impacket.dcerpc.v5.rpcrt import (
    RPC_C_AUTHN_GSS_NEGOTIATE,
    RPC_C_AUTHN_LEVEL_NONE,
    RPC_C_AUTHN_LEVEL_PKT_INTEGRITY,
    RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
    RPC_C_AUTHN_WINNT,
    DCERPCException,
    TypeSerialization1,
)
from impacket.uuid import generate, string_to_bin, uuidtup_to_bin

CLSID_ActivationContextInfo = string_to_bin("000001a5-0000-0000-c000-000000000046")
CLSID_ActivationPropertiesIn = string_to_bin("00000338-0000-0000-c000-000000000046")
CLSID_ActivationPropertiesOut = string_to_bin("00000339-0000-0000-c000-000000000046")
CLSID_CONTEXT_EXTENSION = string_to_bin("00000334-0000-0000-c000-000000000046")
CLSID_ContextMarshaler = string_to_bin("0000033b-0000-0000-c000-000000000046")
CLSID_ERROR_EXTENSION = string_to_bin("0000031c-0000-0000-c000-000000000046")
CLSID_ErrorObject = string_to_bin("0000031b-0000-0000-c000-000000000046")
CLSID_InstanceInfo = string_to_bin("000001ad-0000-0000-c000-000000000046")
CLSID_InstantiationInfo = string_to_bin("000001ab-0000-0000-c000-000000000046")
CLSID_PropsOutInfo = string_to_bin("00000339-0000-0000-c000-000000000046")
CLSID_ScmReplyInfo = string_to_bin("000001b6-0000-0000-c000-000000000046")
CLSID_ScmRequestInfo = string_to_bin("000001aa-0000-0000-c000-000000000046")
CLSID_SecurityInfo = string_to_bin("000001a6-0000-0000-c000-000000000046")
CLSID_ServerLocationInfo = string_to_bin("000001a4-0000-0000-c000-000000000046")
CLSID_SpecialSystemProperties = string_to_bin("000001b9-0000-0000-c000-000000000046")
IID_IActivation = uuidtup_to_bin(("4d9f4ab8-7d1c-11cf-861e-0020af6e7c57", "0.0"))
IID_IActivationPropertiesIn = uuidtup_to_bin(("000001A2-0000-0000-C000-000000000046", "0.0"))
IID_IActivationPropertiesOut = uuidtup_to_bin(("000001A3-0000-0000-C000-000000000046", "0.0"))
IID_IContext = uuidtup_to_bin(("000001c0-0000-0000-C000-000000000046", "0.0"))
IID_IObjectExporter = uuidtup_to_bin(("99fcfec4-5260-101b-bbcb-00aa0021347a", "0.0"))
IID_IRemoteSCMActivator = uuidtup_to_bin(("000001A0-0000-0000-C000-000000000046", "0.0"))
IID_IRemUnknown = uuidtup_to_bin(("00000131-0000-0000-C000-000000000046", "0.0"))
IID_IRemUnknown2 = uuidtup_to_bin(("00000143-0000-0000-C000-000000000046", "0.0"))
IID_IUnknown = uuidtup_to_bin(("00000000-0000-0000-C000-000000000046", "0.0"))
IID_IClassFactory = uuidtup_to_bin(("00000001-0000-0000-C000-000000000046", "0.0"))


class DCERPCSessionError(DCERPCException):
    def __init__(self, error_string=None, error_code=None, packet=None):
        DCERPCException.__init__(self, error_string, error_code, packet)

    def __str__(self):
        if self.error_code in hresult_errors.ERROR_MESSAGES:
            error_msg_short = hresult_errors.ERROR_MESSAGES[self.error_code][0]
            error_msg_verbose = hresult_errors.ERROR_MESSAGES[self.error_code][1]
            return "DCOM SessionError: code: 0x%x - %s - %s" % (self.error_code, error_msg_short, error_msg_verbose)
        else:
            return "DCOM SessionError: unknown error code: 0x%x" % self.error_code


################################################################################
# CONSTANTS
################################################################################
# 2.2.1 OID
OID = ULONGLONG


class OID_ARRAY(NDRUniConformantArray):
    item = OID


class POID_ARRAY(NDRPOINTER):
    referent = (("Data", OID_ARRAY),)


# 2.2.2 SETID
SETID = ULONGLONG

# 2.2.4 error_status_t
error_status_t = ULONG

# 2.2.6 CID
CID = GUID

# 2.2.7 CLSID
CLSID = GUID

# 2.2.8 IID
IID = GUID
PIID = PGUID

# 2.2.9 IPID
IPID = GUID

# 2.2.10 OXID
OXID = ULONGLONG

# 2.2.18 OBJREF
FLAGS_OBJREF_STANDARD = 0x00000001
FLAGS_OBJREF_HANDLER = 0x00000002
FLAGS_OBJREF_CUSTOM = 0x00000004
FLAGS_OBJREF_EXTENDED = 0x00000008

# 2.2.18.1 STDOBJREF
SORF_NOPING = 0x00001000

# 2.2.20 Context
CTXMSHLFLAGS_BYVAL = 0x00000002

# 2.2.20.1 PROPMARSHALHEADER
CPFLAG_PROPAGATE = 0x00000001
CPFLAG_EXPOSE = 0x00000002
CPFLAG_ENVOY = 0x00000004

# 2.2.22.2.1 InstantiationInfoData
ACTVFLAGS_DISABLE_AAA = 0x00000002
ACTVFLAGS_ACTIVATE_32_BIT_SERVER = 0x00000004
ACTVFLAGS_ACTIVATE_64_BIT_SERVER = 0x00000008
ACTVFLAGS_NO_FAILURE_LOG = 0x00000020

# 2.2.22.2.2 SpecialPropertiesData
SPD_FLAG_USE_CONSOLE_SESSION = 0x00000001

# 2.2.28.1 IDL Range Constants
MAX_REQUESTED_INTERFACES = 0x8000
MAX_REQUESTED_PROTSEQS = 0x8000
MIN_ACTPROP_LIMIT = 1
MAX_ACTPROP_LIMIT = 10

################################################################################
# STRUCTURES
################################################################################
class handle_t(NDRSTRUCT):
    structure = (
        ("context_handle_attributes", ULONG),
        ("context_handle_uuid", UUID),
    )

    def __init__(self, data=None, isNDR64=False):
        NDRSTRUCT.__init__(self, data, isNDR64)
        self["context_handle_uuid"] = b"\x00" * 16

    def isNull(self):
        return self["context_handle_uuid"] == b"\x00" * 16


# 2.2.11 COMVERSION
class COMVERSION(NDRSTRUCT):
    structure = (
        ("MajorVersion", USHORT),
        ("MinorVersion", USHORT),
    )

    def __init__(self, data=None, isNDR64=False):
        NDRSTRUCT.__init__(self, data, isNDR64)
        if data is None:
            self["MajorVersion"] = 5
            self["MinorVersion"] = 7


class PCOMVERSION(NDRPOINTER):
    referent = (("Data", COMVERSION),)


# 2.2.13.1 ORPC_EXTENT
# This MUST contain an array of bytes that form the extent data.
# The array size MUST be a multiple of 8 for alignment reasons.
class BYTE_ARRAY(NDRUniConformantArray):
    item = "c"


class ORPC_EXTENT(NDRSTRUCT):
    structure = (
        ("id", GUID),
        ("size", ULONG),
        ("data", BYTE_ARRAY),
    )


# 2.2.13.2 ORPC_EXTENT_ARRAY
# ThisMUSTbeanarrayofORPC_EXTENTs.ThearraysizeMUSTbeamultipleof2for alignment reasons.
class PORPC_EXTENT(NDRPOINTER):
    referent = (("Data", ORPC_EXTENT),)


class EXTENT_ARRAY(NDRUniConformantArray):
    item = PORPC_EXTENT


class PEXTENT_ARRAY(NDRPOINTER):
    referent = (("Data", EXTENT_ARRAY),)


class ORPC_EXTENT_ARRAY(NDRSTRUCT):
    structure = (
        ("size", ULONG),
        ("reserved", ULONG),
        ("extent", PEXTENT_ARRAY),
    )


class PORPC_EXTENT_ARRAY(NDRPOINTER):
    referent = (("Data", ORPC_EXTENT_ARRAY),)


# 2.2.13.3 ORPCTHIS
class ORPCTHIS(NDRSTRUCT):
    structure = (
        ("version", COMVERSION),
        ("flags", ULONG),
        ("reserved1", ULONG),
        ("cid", CID),
        ("extensions", PORPC_EXTENT_ARRAY),
    )


# 2.2.13.4 ORPCTHAT
class ORPCTHAT(NDRSTRUCT):
    structure = (
        ("flags", ULONG),
        ("extensions", PORPC_EXTENT_ARRAY),
    )


# 2.2.14 MInterfacePointer
class MInterfacePointer(NDRSTRUCT):
    structure = (
        ("ulCntData", ULONG),
        ("abData", BYTE_ARRAY),
    )


# 2.2.15 PMInterfacePointerInternal
class PMInterfacePointerInternal(NDRPOINTER):
    referent = (("Data", MInterfacePointer),)


# 2.2.16 PMInterfacePointer
class PMInterfacePointer(NDRPOINTER):
    referent = (("Data", MInterfacePointer),)


class PPMInterfacePointer(NDRPOINTER):
    referent = (("Data", PMInterfacePointer),)


# 2.2.18 OBJREF
class OBJREF(NDRSTRUCT):
    commonHdr = (
        ("signature", ULONG),
        ("flags", ULONG),
        ("iid", GUID),
    )

    def __init__(self, data=None, isNDR64=False):
        NDRSTRUCT.__init__(self, data, isNDR64)
        if data is None:
            self["signature"] = 0x574F454D


# 2.2.18.1 STDOBJREF
class STDOBJREF(NDRSTRUCT):
    structure = (
        ("flags", ULONG),
        ("cPublicRefs", ULONG),
        ("oxid", OXID),
        ("oid", OID),
        ("ipid", IPID),
    )


# 2.2.18.4 OBJREF_STANDARD
class OBJREF_STANDARD(OBJREF):
    structure = (
        ("std", STDOBJREF),
        ("saResAddr", ":"),
    )

    def __init__(self, data=None, isNDR64=False):
        OBJREF.__init__(self, data, isNDR64)
        if data is None:
            self["flags"] = FLAGS_OBJREF_STANDARD


# 2.2.18.5 OBJREF_HANDLER
class OBJREF_HANDLER(OBJREF):
    structure = (
        ("std", STDOBJREF),
        ("clsid", CLSID),
        ("saResAddr", ":"),
    )

    def __init__(self, data=None, isNDR64=False):
        OBJREF.__init__(self, data, isNDR64)
        if data is None:
            self["flags"] = FLAGS_OBJREF_HANDLER


# 2.2.18.6 OBJREF_CUSTOM
class OBJREF_CUSTOM(OBJREF):
    structure = (
        ("clsid", CLSID),
        ("cbExtension", ULONG),
        ("ObjectReferenceSize", ULONG),
        ("pObjectData", ":"),
    )

    def __init__(self, data=None, isNDR64=False):
        OBJREF.__init__(self, data, isNDR64)
        if data is None:
            self["flags"] = FLAGS_OBJREF_CUSTOM


# 2.2.18.8 DATAELEMENT
class DATAELEMENT(NDRSTRUCT):
    structure = (
        ("dataID", GUID),
        ("cbSize", ULONG),
        ("cbRounded", ULONG),
        ("Data", ":"),
    )


class DUALSTRINGARRAYPACKED(NDRSTRUCT):
    structure = (
        ("wNumEntries", USHORT),
        ("wSecurityOffset", USHORT),
        ("aStringArray", ":"),
    )

    def getDataLen(self, data, offset=0):
        return self["wNumEntries"] * 2


# 2.2.18.7 OBJREF_EXTENDED
class OBJREF_EXTENDED(OBJREF):
    structure = (
        ("std", STDOBJREF),
        ("Signature1", ULONG),
        ("saResAddr", DUALSTRINGARRAYPACKED),
        ("nElms", ULONG),
        ("Signature2", ULONG),
        ("ElmArray", DATAELEMENT),
    )

    def __init__(self, data=None, isNDR64=False):
        OBJREF.__init__(self, data, isNDR64)
        if data is None:
            self["flags"] = FLAGS_OBJREF_EXTENDED
            self["Signature1"] = 0x4E535956
            self["Signature1"] = 0x4E535956
            self["nElms"] = 0x4E535956


# 2.2.19 DUALSTRINGARRAY
class USHORT_ARRAY(NDRUniConformantArray):
    item = "<H"


class PUSHORT_ARRAY(NDRPOINTER):
    referent = (("Data", USHORT_ARRAY),)


class DUALSTRINGARRAY(NDRSTRUCT):
    structure = (
        ("wNumEntries", USHORT),
        ("wSecurityOffset", USHORT),
        ("aStringArray", USHORT_ARRAY),
    )


class PDUALSTRINGARRAY(NDRPOINTER):
    referent = (("Data", DUALSTRINGARRAY),)


# 2.2.19.3 STRINGBINDING
class STRINGBINDING(NDRSTRUCT):
    structure = (
        ("wTowerId", USHORT),
        ("aNetworkAddr", WIDESTR),
    )


# 2.2.19.4 SECURITYBINDING
class SECURITYBINDING(NDRSTRUCT):
    structure = (
        ("wAuthnSvc", USHORT),
        ("Reserved", USHORT),
        ("aPrincName", WIDESTR),
    )


# 2.2.20.1 PROPMARSHALHEADER
class PROPMARSHALHEADER(NDRSTRUCT):
    structure = (
        ("clsid", CLSID),
        ("policyId", GUID),
        ("flags", ULONG),
        ("cb", ULONG),
        ("ctxProperty", ":"),
    )


class PROPMARSHALHEADER_ARRAY(NDRUniConformantArray):
    item = PROPMARSHALHEADER


# 2.2.20 Context
class Context(NDRSTRUCT):
    structure = (
        ("MajorVersion", USHORT),
        ("MinVersion", USHORT),
        ("ContextId", GUID),
        ("Flags", ULONG),
        ("Reserved", ULONG),
        ("dwNumExtents", ULONG),
        ("cbExtents", ULONG),
        ("MshlFlags", ULONG),
        ("Count", ULONG),
        ("Frozen", ULONG),
        ("PropMarshalHeader", PROPMARSHALHEADER_ARRAY),
    )


# 2.2.21.3 ErrorInfoString
class ErrorInfoString(NDRSTRUCT):
    structure = (
        ("dwMax", ULONG),
        ("dwOffSet", ULONG),
        ("dwActual", IID),
        ("Name", WSTR),
    )


# 2.2.21.2 Custom-Marshaled Error Information Format
class ORPC_ERROR_INFORMATION(NDRSTRUCT):
    structure = (
        ("dwVersion", ULONG),
        ("dwHelpContext", ULONG),
        ("iid", IID),
        ("dwSourceSignature", ULONG),
        ("Source", ErrorInfoString),
        ("dwDescriptionSignature", ULONG),
        ("Description", ErrorInfoString),
        ("dwHelpFileSignature", ULONG),
        ("HelpFile", ErrorInfoString),
    )


# 2.2.21.5 EntryHeader
class EntryHeader(NDRSTRUCT):
    structure = (
        ("Signature", ULONG),
        ("cbEHBuffer", ULONG),
        ("cbSize", ULONG),
        ("reserved", ULONG),
        ("policyID", GUID),
    )


class EntryHeader_ARRAY(NDRUniConformantArray):
    item = EntryHeader


# 2.2.21.4 Context ORPC Extension
class ORPC_CONTEXT(NDRSTRUCT):
    structure = (
        ("SignatureVersion", ULONG),
        ("Version", ULONG),
        ("cPolicies", ULONG),
        ("cbBuffer", ULONG),
        ("cbSize", ULONG),
        ("hr", ULONG),
        ("hrServer", ULONG),
        ("reserved", ULONG),
        ("EntryHeader", EntryHeader_ARRAY),
        ("PolicyData", ":"),
    )

    def __init__(self, data=None, isNDR64=False):
        NDRSTRUCT.__init__(self, data, isNDR64)
        if data is None:
            self["SignatureVersion"] = 0x414E554B


# 2.2.22.1 CustomHeader
class CLSID_ARRAY(NDRUniConformantArray):
    item = CLSID


class PCLSID_ARRAY(NDRPOINTER):
    referent = (("Data", CLSID_ARRAY),)


class DWORD_ARRAY(NDRUniConformantArray):
    item = DWORD


class PDWORD_ARRAY(NDRPOINTER):
    referent = (("Data", DWORD_ARRAY),)


class CustomHeader(TypeSerialization1):
    structure = (
        ("totalSize", DWORD),
        ("headerSize", DWORD),
        ("dwReserved", DWORD),
        ("destCtx", DWORD),
        ("cIfs", DWORD),
        ("classInfoClsid", CLSID),
        ("pclsid", PCLSID_ARRAY),
        ("pSizes", PDWORD_ARRAY),
        ("pdwReserved", LPLONG),
        # ('pdwReserved',LONG),
    )

    def getData(self, soFar=0):
        self["headerSize"] = len(TypeSerialization1.getData(self, soFar)) + len(
            TypeSerialization1.getDataReferents(self, soFar)
        )
        self["cIfs"] = len(self["pclsid"])
        return TypeSerialization1.getData(self, soFar)


# 2.2.22 Activation Properties BLOB
class ACTIVATION_BLOB(NDRTLSTRUCT):
    structure = (
        ("dwSize", ULONG),
        ("dwReserved", ULONG),
        ("CustomHeader", CustomHeader),
        ("Property", UNKNOWNDATA),
    )

    def getData(self, soFar=0):
        self["dwSize"] = (
            len(self["CustomHeader"].getData(soFar))
            + len(self["CustomHeader"].getDataReferents(soFar))
            + len(self["Property"])
        )
        self["CustomHeader"]["totalSize"] = self["dwSize"]
        return NDRTLSTRUCT.getData(self)


# 2.2.22.2.1 InstantiationInfoData
class IID_ARRAY(NDRUniConformantArray):
    item = IID


class PIID_ARRAY(NDRPOINTER):
    referent = (("Data", IID_ARRAY),)


class InstantiationInfoData(TypeSerialization1):
    structure = (
        ("classId", CLSID),
        ("classCtx", DWORD),
        ("actvflags", DWORD),
        ("fIsSurrogate", LONG),
        ("cIID", DWORD),
        ("instFlag", DWORD),
        ("pIID", PIID_ARRAY),
        ("thisSize", DWORD),
        ("clientCOMVersion", COMVERSION),
    )


# 2.2.22.2.2 SpecialPropertiesData
class SpecialPropertiesData(TypeSerialization1):
    structure = (
        ("dwSessionId", ULONG),
        ("fRemoteThisSessionId", LONG),
        ("fClientImpersonating", LONG),
        ("fPartitionIDPresent", LONG),
        ("dwDefaultAuthnLvl", DWORD),
        ("guidPartition", GUID),
        ("dwPRTFlags", DWORD),
        ("dwOrigClsctx", DWORD),
        ("dwFlags", DWORD),
        ("Reserved0", DWORD),
        ("Reserved0", DWORD),
        ("Reserved", '32s=""'),
        # ('Reserved1',DWORD),
        # ('Reserved2',ULONGLONG),
        # ('Reserved3_1',DWORD),
        # ('Reserved3_2',DWORD),
        # ('Reserved3_3',DWORD),
        # ('Reserved3_4',DWORD),
        # ('Reserved3_5',DWORD),
    )


# 2.2.22.2.3 InstanceInfoData
class InstanceInfoData(TypeSerialization1):
    structure = (
        ("fileName", LPWSTR),
        ("mode", DWORD),
        ("ifdROT", PMInterfacePointer),
        ("ifdStg", PMInterfacePointer),
    )


# 2.2.22.2.4.1 customREMOTE_REQUEST_SCM_INFO
class customREMOTE_REQUEST_SCM_INFO(NDRSTRUCT):
    structure = (
        ("ClientImpLevel", DWORD),
        ("cRequestedProtseqs", USHORT),
        ("pRequestedProtseqs", PUSHORT_ARRAY),
    )


class PcustomREMOTE_REQUEST_SCM_INFO(NDRPOINTER):
    referent = (("Data", customREMOTE_REQUEST_SCM_INFO),)


# 2.2.22.2.4 ScmRequestInfoData
class ScmRequestInfoData(TypeSerialization1):
    structure = (
        ("pdwReserved", LPLONG),
        ("remoteRequest", PcustomREMOTE_REQUEST_SCM_INFO),
    )


# 2.2.22.2.5 ActivationContextInfoData
class ActivationContextInfoData(TypeSerialization1):
    structure = (
        ("clientOK", LONG),
        ("bReserved1", LONG),
        ("dwReserved1", DWORD),
        ("dwReserved2", DWORD),
        ("pIFDClientCtx", PMInterfacePointer),
        ("pIFDPrototypeCtx", PMInterfacePointer),
    )


# 2.2.22.2.6 LocationInfoData
class LocationInfoData(TypeSerialization1):
    structure = (
        ("machineName", LPWSTR),
        ("processId", DWORD),
        ("apartmentId", DWORD),
        ("contextId", DWORD),
    )


# 2.2.22.2.7.1 COSERVERINFO
class COSERVERINFO(NDRSTRUCT):
    structure = (
        ("dwReserved1", DWORD),
        ("pwszName", LPWSTR),
        ("pdwReserved", LPLONG),
        ("dwReserved2", DWORD),
    )


class PCOSERVERINFO(NDRPOINTER):
    referent = (("Data", COSERVERINFO),)


# 2.2.22.2.7 SecurityInfoData
class SecurityInfoData(TypeSerialization1):
    structure = (
        ("dwAuthnFlags", DWORD),
        ("pServerInfo", PCOSERVERINFO),
        ("pdwReserved", LPLONG),
    )


# 2.2.22.2.8.1 customREMOTE_REPLY_SCM_INFO
class customREMOTE_REPLY_SCM_INFO(NDRSTRUCT):
    structure = (
        ("Oxid", OXID),
        ("pdsaOxidBindings", PDUALSTRINGARRAY),
        ("ipidRemUnknown", IPID),
        ("authnHint", DWORD),
        ("serverVersion", COMVERSION),
    )


class PcustomREMOTE_REPLY_SCM_INFO(NDRPOINTER):
    referent = (("Data", customREMOTE_REPLY_SCM_INFO),)


# 2.2.22.2.8 ScmReplyInfoData
class ScmReplyInfoData(TypeSerialization1):
    structure = (
        ("pdwReserved", DWORD),
        ("remoteReply", PcustomREMOTE_REPLY_SCM_INFO),
    )


# 2.2.22.2.9 PropsOutInfo
class HRESULT_ARRAY(NDRUniConformantArray):
    item = HRESULT


class PHRESULT_ARRAY(NDRPOINTER):
    referent = (("Data", HRESULT_ARRAY),)


class MInterfacePointer_ARRAY(NDRUniConformantArray):
    item = MInterfacePointer


class PMInterfacePointer_ARRAY(NDRUniConformantArray):
    item = PMInterfacePointer


class PPMInterfacePointer_ARRAY(NDRPOINTER):
    referent = (("Data", PMInterfacePointer_ARRAY),)


class PropsOutInfo(TypeSerialization1):
    structure = (
        ("cIfs", DWORD),
        ("piid", PIID_ARRAY),
        ("phresults", PHRESULT_ARRAY),
        ("ppIntfData", PPMInterfacePointer_ARRAY),
    )


# 2.2.23 REMINTERFACEREF
class REMINTERFACEREF(NDRSTRUCT):
    structure = (
        ("ipid", IPID),
        ("cPublicRefs", LONG),
        ("cPrivateRefs", LONG),
    )


class REMINTERFACEREF_ARRAY(NDRUniConformantArray):
    item = REMINTERFACEREF


# 2.2.24 REMQIRESULT
class REMQIRESULT(NDRSTRUCT):
    structure = (
        ("hResult", HRESULT),
        ("std", STDOBJREF),
    )


# 2.2.25 PREMQIRESULT
class PREMQIRESULT(NDRPOINTER):
    referent = (("Data", REMQIRESULT),)


# 2.2.26 REFIPID
REFIPID = GUID

################################################################################
# RPC CALLS
################################################################################
class DCOMCALL(NDRCALL):
    commonHdr = (("ORPCthis", ORPCTHIS),)


class DCOMANSWER(NDRCALL):
    commonHdr = (("ORPCthat", ORPCTHAT),)


# 3.1.2.5.1.1 IObjectExporter::ResolveOxid (Opnum 0)
class ResolveOxid(NDRCALL):
    opnum = 0
    structure = (
        ("pOxid", OXID),
        ("cRequestedProtseqs", USHORT),
        ("arRequestedProtseqs", USHORT_ARRAY),
    )


class ResolveOxidResponse(NDRCALL):
    structure = (
        ("ppdsaOxidBindings", PDUALSTRINGARRAY),
        ("pipidRemUnknown", IPID),
        ("pAuthnHint", DWORD),
        ("ErrorCode", error_status_t),
    )


# 3.1.2.5.1.2 IObjectExporter::SimplePing (Opnum 1)
class SimplePing(NDRCALL):
    opnum = 1
    structure = (("pSetId", SETID),)


class SimplePingResponse(NDRCALL):
    structure = (("ErrorCode", error_status_t),)


# 3.1.2.5.1.3 IObjectExporter::ComplexPing (Opnum 2)
class ComplexPing(NDRCALL):
    opnum = 2
    structure = (
        ("pSetId", SETID),
        ("SequenceNum", USHORT),
        ("cAddToSet", USHORT),
        ("cDelFromSet", USHORT),
        ("AddToSet", POID_ARRAY),
        ("DelFromSet", POID_ARRAY),
    )


class ComplexPingResponse(NDRCALL):
    structure = (
        ("pSetId", SETID),
        ("pPingBackoffFactor", USHORT),
        ("ErrorCode", error_status_t),
    )


# 3.1.2.5.1.4 IObjectExporter::ServerAlive (Opnum 3)
class ServerAlive(NDRCALL):
    opnum = 3
    structure = ()


class ServerAliveResponse(NDRCALL):
    structure = (("ErrorCode", error_status_t),)


# 3.1.2.5.1.5 IObjectExporter::ResolveOxid2 (Opnum 4)
class ResolveOxid2(NDRCALL):
    opnum = 4
    structure = (
        ("pOxid", OXID),
        ("cRequestedProtseqs", USHORT),
        ("arRequestedProtseqs", USHORT_ARRAY),
    )


class ResolveOxid2Response(NDRCALL):
    structure = (
        ("ppdsaOxidBindings", PDUALSTRINGARRAY),
        ("pipidRemUnknown", IPID),
        ("pAuthnHint", DWORD),
        ("pComVersion", COMVERSION),
        ("ErrorCode", error_status_t),
    )


# 3.1.2.5.1.6 IObjectExporter::ServerAlive2 (Opnum 5)
class ServerAlive2(NDRCALL):
    opnum = 5
    structure = ()


class ServerAlive2Response(NDRCALL):
    structure = (
        ("pComVersion", COMVERSION),
        ("ppdsaOrBindings", PDUALSTRINGARRAY),
        ("pReserved", LPLONG),
        ("ErrorCode", error_status_t),
    )


# 3.1.2.5.2.3.1 IActivation:: RemoteActivation (Opnum 0)
class RemoteActivation(NDRCALL):
    opnum = 0
    structure = (
        ("ORPCthis", ORPCTHIS),
        ("Clsid", GUID),
        ("pwszObjectName", LPWSTR),
        ("pObjectStorage", PMInterfacePointer),
        ("ClientImpLevel", DWORD),
        ("Mode", DWORD),
        ("Interfaces", DWORD),
        ("pIIDs", PIID_ARRAY),
        ("cRequestedProtseqs", USHORT),
        ("aRequestedProtseqs", USHORT_ARRAY),
    )


class RemoteActivationResponse(NDRCALL):
    structure = (
        ("ORPCthat", ORPCTHAT),
        ("pOxid", OXID),
        ("ppdsaOxidBindings", PDUALSTRINGARRAY),
        ("pipidRemUnknown", IPID),
        ("pAuthnHint", DWORD),
        ("pServerVersion", COMVERSION),
        ("phr", HRESULT),
        ("ppInterfaceData", PMInterfacePointer_ARRAY),
        ("pResults", HRESULT_ARRAY),
        ("ErrorCode", error_status_t),
    )


# 3.1.2.5.2.3.2 IRemoteSCMActivator:: RemoteGetClassObject (Opnum 3)
class RemoteGetClassObject(NDRCALL):
    opnum = 3
    structure = (
        ("ORPCthis", ORPCTHIS),
        ("pActProperties", PMInterfacePointer),
    )


class RemoteGetClassObjectResponse(NDRCALL):
    structure = (
        ("ORPCthat", ORPCTHAT),
        ("ppActProperties", PMInterfacePointer),
        ("ErrorCode", error_status_t),
    )


# 3.1.2.5.2.3.3 IRemoteSCMActivator::RemoteCreateInstance (Opnum 4)
class RemoteCreateInstance(NDRCALL):
    opnum = 4
    structure = (
        ("ORPCthis", ORPCTHIS),
        ("pUnkOuter", PMInterfacePointer),
        ("pActProperties", PMInterfacePointer),
    )


class RemoteCreateInstanceResponse(NDRCALL):
    structure = (
        ("ORPCthat", ORPCTHAT),
        ("ppActProperties", PMInterfacePointer),
        ("ErrorCode", error_status_t),
    )


# 3.1.1.5.6.1.1 IRemUnknown::RemQueryInterface (Opnum 3)
class RemQueryInterface(DCOMCALL):
    opnum = 3
    structure = (
        ("ripid", REFIPID),
        ("cRefs", ULONG),
        ("cIids", USHORT),
        ("iids", IID_ARRAY),
    )


class RemQueryInterfaceResponse(DCOMANSWER):
    structure = (
        ("ppQIResults", PREMQIRESULT),
        ("ErrorCode", error_status_t),
    )


# 3.1.1.5.6.1.2 IRemUnknown::RemAddRef (Opnum 4 )
class RemAddRef(DCOMCALL):
    opnum = 4
    structure = (
        ("cInterfaceRefs", USHORT),
        ("InterfaceRefs", REMINTERFACEREF_ARRAY),
    )


class RemAddRefResponse(DCOMANSWER):
    structure = (
        ("pResults", DWORD_ARRAY),
        ("ErrorCode", error_status_t),
    )


# 3.1.1.5.6.1.3 IRemUnknown::RemRelease (Opnum 5)
class RemRelease(DCOMCALL):
    opnum = 5
    structure = (
        ("cInterfaceRefs", USHORT),
        ("InterfaceRefs", REMINTERFACEREF_ARRAY),
    )


class RemReleaseResponse(DCOMANSWER):
    structure = (("ErrorCode", error_status_t),)


################################################################################
# OPNUMs and their corresponding structures
################################################################################
OPNUMS = {}

################################################################################
# HELPER FUNCTIONS
################################################################################
class DCOMConnection:
    """
    This class represents a DCOM Connection. It is in charge of establishing the
    DCE connection against the portmap, and then launch a thread that will be
    pinging the objects created against the target.
    In theory, there should be a single instance of this class for every target
    """

    PINGTIMER = None
    OID_ADD = {}
    OID_DEL = {}
    OID_SET = {}
    PORTMAPS = {}

    def __init__(
        self,
        target,
        username="",
        password="",
        domain="",
        lmhash="",
        nthash="",
        aesKey="",
        TGT=None,
        TGS=None,
        authLevel=RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
        oxidResolver=False,
        doKerberos=False,
        kdcHost=None,
    ):
        self.__target = target
        self.__userName = username
        self.__password = password
        self.__domain = domain
        self.__lmhash = lmhash
        self.__nthash = nthash
        self.__aesKey = aesKey
        self.__TGT = TGT
        self.__TGS = TGS
        self.__authLevel = authLevel
        self.__portmap = None
        self.__oxidResolver = oxidResolver
        self.__doKerberos = doKerberos
        self.__kdcHost = kdcHost
        self.initConnection()

    @classmethod
    def addOid(cls, target, oid):
        if (target in DCOMConnection.OID_ADD) is False:
            DCOMConnection.OID_ADD[target] = set()
        DCOMConnection.OID_ADD[target].add(oid)
        if (target in DCOMConnection.OID_SET) is False:
            DCOMConnection.OID_SET[target] = {}
            DCOMConnection.OID_SET[target]["oids"] = set()
            DCOMConnection.OID_SET[target]["setid"] = 0

    @classmethod
    def delOid(cls, target, oid):
        if (target in DCOMConnection.OID_DEL) is False:
            DCOMConnection.OID_DEL[target] = set()
        DCOMConnection.OID_DEL[target].add(oid)
        if (target in DCOMConnection.OID_SET) is False:
            DCOMConnection.OID_SET[target] = {}
            DCOMConnection.OID_SET[target]["oids"] = set()
            DCOMConnection.OID_SET[target]["setid"] = 0

    @classmethod
    def pingServer(cls):
        # Here we need to go through all the objects opened and ping them.
        # ToDo: locking for avoiding race conditions
        # print DCOMConnection.PORTMAPS
        # print DCOMConnection.OID_SET
        try:
            for target in DCOMConnection.OID_SET:
                addedOids = set()
                deletedOids = set()
                if target in DCOMConnection.OID_ADD:
                    addedOids = DCOMConnection.OID_ADD[target]
                    del DCOMConnection.OID_ADD[target]

                if target in DCOMConnection.OID_DEL:
                    deletedOids = DCOMConnection.OID_DEL[target]
                    del DCOMConnection.OID_DEL[target]

                objExporter = IObjectExporter(DCOMConnection.PORTMAPS[target])

                if len(addedOids) > 0 or len(deletedOids) > 0:
                    if "setid" in DCOMConnection.OID_SET[target]:
                        setId = DCOMConnection.OID_SET[target]["setid"]
                    else:
                        setId = 0
                    resp = objExporter.ComplexPing(setId, 0, addedOids, deletedOids)
                    DCOMConnection.OID_SET[target]["oids"] -= deletedOids
                    DCOMConnection.OID_SET[target]["oids"] |= addedOids
                    DCOMConnection.OID_SET[target]["setid"] = resp["pSetId"]
                else:
                    objExporter.SimplePing(DCOMConnection.OID_SET[target]["setid"])
        except Exception as e:
            # There might be exceptions when sending packets
            # We should try to continue tho.
            LOG.error(str(e))
            pass

        DCOMConnection.PINGTIMER = Timer(120, DCOMConnection.pingServer)
        try:
            DCOMConnection.PINGTIMER.start()
        except Exception as e:
            if str(e).find("threads can only be started once") < 0:
                raise e

    def initTimer(self):
        if self.__oxidResolver is True:
            if DCOMConnection.PINGTIMER is None:
                DCOMConnection.PINGTIMER = Timer(120, DCOMConnection.pingServer)
            try:
                DCOMConnection.PINGTIMER.start()
            except Exception as e:
                if str(e).find("threads can only be started once") < 0:
                    raise e

    def initConnection(self):
        stringBinding = r"ncacn_ip_tcp:%s" % self.__target
        rpctransport = transport.DCERPCTransportFactory(stringBinding)

        if hasattr(rpctransport, "set_credentials") and len(self.__userName) >= 0:
            # This method exists only for selected protocol sequences.
            rpctransport.set_credentials(
                self.__userName,
                self.__password,
                self.__domain,
                self.__lmhash,
                self.__nthash,
                self.__aesKey,
                self.__TGT,
                self.__TGS,
            )
            rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
        self.__portmap = rpctransport.get_dce_rpc()
        self.__portmap.set_auth_level(self.__authLevel)
        if self.__doKerberos is True:
            self.__portmap.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
        self.__portmap.connect()
        DCOMConnection.PORTMAPS[self.__target] = self.__portmap

    def CoCreateInstanceEx(self, clsid, iid):
        scm = IRemoteSCMActivator(self.__portmap)
        iInterface = scm.RemoteCreateInstance(clsid, iid)
        self.initTimer()
        return iInterface

    def get_dce_rpc(self):
        return DCOMConnection.PORTMAPS[self.__target]

    def disconnect(self):
        if DCOMConnection.PINGTIMER is not None:
            del DCOMConnection.PORTMAPS[self.__target]
            del DCOMConnection.OID_SET[self.__target]
            if len(DCOMConnection.PORTMAPS) == 0:
                # This means there are no more clients using this object, kill it
                DCOMConnection.PINGTIMER.cancel()
                DCOMConnection.PINGTIMER.join()
                DCOMConnection.PINGTIMER = None
        if self.__target in INTERFACE.CONNECTIONS:
            del INTERFACE.CONNECTIONS[self.__target][currentThread().getName()]
        self.__portmap.disconnect()
        # print INTERFACE.CONNECTIONS


class CLASS_INSTANCE:
    def __init__(self, ORPCthis, stringBinding):
        self.__stringBindings = stringBinding
        self.__ORPCthis = ORPCthis
        self.__authType = RPC_C_AUTHN_WINNT
        self.__authLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY

    def get_ORPCthis(self):
        return self.__ORPCthis

    def get_string_bindings(self):
        return self.__stringBindings

    def get_auth_level(self):
        if RPC_C_AUTHN_LEVEL_NONE < self.__authLevel < RPC_C_AUTHN_LEVEL_PKT_PRIVACY:
            if self.__authType == RPC_C_AUTHN_WINNT:
                return RPC_C_AUTHN_LEVEL_PKT_INTEGRITY
            else:
                return RPC_C_AUTHN_LEVEL_PKT_PRIVACY
        return self.__authLevel

    def set_auth_level(self, level):
        self.__authLevel = level

    def get_auth_type(self):
        return self.__authType

    def set_auth_type(self, authType):
        self.__authType = authType


class INTERFACE:
    # class variable holding the transport connections, organized by target IP
    CONNECTIONS = {}

    def __init__(
        self,
        cinstance=None,
        objRef=None,
        ipidRemUnknown=None,
        iPid=None,
        oxid=None,
        oid=None,
        target=None,
        interfaceInstance=None,
    ):
        if interfaceInstance is not None:
            self.__target = interfaceInstance.get_target()
            self.__iPid = interfaceInstance.get_iPid()
            self.__oid = interfaceInstance.get_oid()
            self.__oxid = interfaceInstance.get_oxid()
            self.__cinstance = interfaceInstance.get_cinstance()
            self.__objRef = interfaceInstance.get_objRef()
            self.__ipidRemUnknown = interfaceInstance.get_ipidRemUnknown()
        else:
            if target is None:
                raise Exception("No target")
            self.__target = target
            self.__iPid = iPid
            self.__oid = oid
            self.__oxid = oxid
            self.__cinstance = cinstance
            self.__objRef = objRef
            self.__ipidRemUnknown = ipidRemUnknown
            # We gotta check if we have a container inside our connection list, if not, create
            if (self.__target in INTERFACE.CONNECTIONS) is not True:
                INTERFACE.CONNECTIONS[self.__target] = {}
                INTERFACE.CONNECTIONS[self.__target][currentThread().getName()] = {}

            if objRef is not None:
                self.process_interface(objRef)

    def process_interface(self, data):
        objRefType = OBJREF(data)["flags"]
        objRef = None
        if objRefType == FLAGS_OBJREF_CUSTOM:
            objRef = OBJREF_CUSTOM(data)
        elif objRefType == FLAGS_OBJREF_HANDLER:
            objRef = OBJREF_HANDLER(data)
        elif objRefType == FLAGS_OBJREF_STANDARD:
            objRef = OBJREF_STANDARD(data)
        elif objRefType == FLAGS_OBJREF_EXTENDED:
            objRef = OBJREF_EXTENDED(data)
        else:
            LOG.error("Unknown OBJREF Type! 0x%x" % objRefType)

        if objRefType != FLAGS_OBJREF_CUSTOM:
            if objRef["std"]["flags"] & SORF_NOPING == 0:
                DCOMConnection.addOid(self.__target, objRef["std"]["oid"])
            self.__iPid = objRef["std"]["ipid"]
            self.__oid = objRef["std"]["oid"]
            self.__oxid = objRef["std"]["oxid"]
            if self.__oxid is None:
                objRef.dump()
                raise Exception("OXID is None")

    def get_oxid(self):
        return self.__oxid

    def set_oxid(self, oxid):
        self.__oxid = oxid

    def get_oid(self):
        return self.__oid

    def set_oid(self, oid):
        self.__oid = oid

    def get_target(self):
        return self.__target

    def get_iPid(self):
        return self.__iPid

    def set_iPid(self, iPid):
        self.__iPid = iPid

    def get_objRef(self):
        return self.__objRef

    def set_objRef(self, objRef):
        self.__objRef = objRef

    def get_ipidRemUnknown(self):
        return self.__ipidRemUnknown

    def get_dce_rpc(self):
        return INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]["dce"]

    def get_cinstance(self):
        return self.__cinstance

    def set_cinstance(self, cinstance):
        self.__cinstance = cinstance

    def is_fdqn(self):
        # I will assume the following
        # If I can't socket.inet_aton() then it's not an IPv4 address
        # Same for ipv6, but since socket.inet_pton is not available in Windows, I'll look for ':'. There can't be
        # an FQDN with ':'
        # Is it isn't both, then it is a FDQN
        try:
            socket.inet_aton(self.__target)
        except:
            # Not an IPv4
            try:
                self.__target.index(":")
            except:
                # Not an IPv6, it's a FDQN
                return True
        return False

    def connect(self, iid=None):
        if (self.__target in INTERFACE.CONNECTIONS) is True:
            if (
                currentThread().getName() in INTERFACE.CONNECTIONS[self.__target]
                and (self.__oxid in INTERFACE.CONNECTIONS[self.__target][currentThread().getName()]) is True
            ):
                dce = INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]["dce"]
                currentBinding = INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid][
                    "currentBinding"
                ]
                if currentBinding == iid:
                    # We don't need to alter_ctx
                    pass
                else:
                    newDce = dce.alter_ctx(iid)
                    INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]["dce"] = newDce
                    INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]["currentBinding"] = iid
            else:
                stringBindings = self.get_cinstance().get_string_bindings()
                # No OXID present, we should create a new connection and store it
                stringBinding = None
                isTargetFDQN = self.is_fdqn()
                LOG.debug("Target system is %s and isFDQN is %s" % (self.get_target(), isTargetFDQN))
                for strBinding in stringBindings:
                    # Here, depending on the get_target() value several things can happen
                    # 1) it's an IPv4 address
                    # 2) it's an IPv6 address
                    # 3) it's a NetBios Name
                    # we should handle all this cases accordingly
                    # Does this match exactly what get_target() returns?
                    LOG.debug("StringBinding: %s" % strBinding["aNetworkAddr"])
                    if strBinding["wTowerId"] == 7:
                        # If there's port information, let's strip it for now.
                        if strBinding["aNetworkAddr"].find("[") >= 0:
                            binding, _, bindingPort = strBinding["aNetworkAddr"].partition("[")
                            bindingPort = "[" + bindingPort
                        else:
                            binding = strBinding["aNetworkAddr"]
                            bindingPort = ""

                        if binding.upper().find(self.get_target().upper()) >= 0:
                            stringBinding = "ncacn_ip_tcp:" + strBinding["aNetworkAddr"][:-1]
                            break
                        # If get_target() is a FQDN, does it match the hostname?
                        elif isTargetFDQN and binding.upper().find(self.get_target().upper().partition(".")[0]) >= 0:
                            # Here we replace the aNetworkAddr with self.get_target()
                            # This is to help resolving the target system name.
                            # self.get_target() has been resolved already otherwise we wouldn't be here whereas
                            # aNetworkAddr is usually the NetBIOS name and unless you have your DNS resolver
                            # with the right suffixes it will probably not resolve right.
                            stringBinding = "ncacn_ip_tcp:%s%s" % (self.get_target(), bindingPort)
                            break

                LOG.debug("StringBinding chosen: %s" % stringBinding)
                if stringBinding is None:
                    # Something wen't wrong, let's just report it
                    raise Exception("Can't find a valid stringBinding to connect")

                dcomInterface = transport.DCERPCTransportFactory(stringBinding)
                if hasattr(dcomInterface, "set_credentials"):
                    # This method exists only for selected protocol sequences.
                    dcomInterface.set_credentials(*DCOMConnection.PORTMAPS[self.__target].get_credentials())
                    dcomInterface.set_kerberos(
                        DCOMConnection.PORTMAPS[self.__target].get_rpc_transport().get_kerberos(),
                        DCOMConnection.PORTMAPS[self.__target].get_rpc_transport().get_kdcHost(),
                    )
                dcomInterface.set_connect_timeout(300)
                dce = dcomInterface.get_dce_rpc()

                if iid is None:
                    raise Exception("IID is None")
                else:
                    dce.set_auth_level(self.__cinstance.get_auth_level())
                    dce.set_auth_type(self.__cinstance.get_auth_type())

                dce.connect()

                if iid is None:
                    raise Exception("IID is None")
                else:
                    dce.bind(iid)

                if self.__oxid is None:
                    # import traceback
                    # traceback.print_stack()
                    raise Exception("OXID NONE, something wrong!!!")

                INTERFACE.CONNECTIONS[self.__target][currentThread().getName()] = {}
                INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid] = {}
                INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]["dce"] = dce
                INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]["currentBinding"] = iid
        else:
            # No connection created
            raise Exception("No connection created")

    def request(self, req, iid=None, uuid=None):
        req["ORPCthis"] = self.get_cinstance().get_ORPCthis()
        req["ORPCthis"]["flags"] = 0
        self.connect(iid)
        dce = self.get_dce_rpc()
        try:
            resp = dce.request(req, uuid)
        except Exception as e:
            if str(e).find("RPC_E_DISCONNECTED") >= 0:
                msg = str(e) + "\n"
                msg += "DCOM keep-alive pinging it might not be working as expected. You can't be idle for more than 14 minutes!\n"
                msg += "You should exit the app and start again\n"
                raise DCERPCException(msg)
            else:
                raise
        return resp

    def disconnect(self):
        return INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]["dce"].disconnect()


# 3.1.1.5.6.1 IRemUnknown Methods
class IRemUnknown(INTERFACE):
    def __init__(self, interface):
        self._iid = IID_IRemUnknown
        # INTERFACE.__init__(self, interface.get_cinstance(), interface.get_objRef(), interface.get_ipidRemUnknown(),
        #                   interface.get_iPid(), target=interface.get_target())
        INTERFACE.__init__(self, interfaceInstance=interface)
        self.set_oxid(interface.get_oxid())

    def RemQueryInterface(self, cRefs, iids):
        # For now, it only supports a single IID
        request = RemQueryInterface()
        request["ORPCthis"] = self.get_cinstance().get_ORPCthis()
        request["ORPCthis"]["flags"] = 0
        request["ripid"] = self.get_iPid()
        request["cRefs"] = cRefs
        request["cIids"] = len(iids)
        for iid in iids:
            _iid = IID()
            _iid["Data"] = iid
            request["iids"].append(_iid)
        resp = self.request(request, IID_IRemUnknown, self.get_ipidRemUnknown())
        # resp.dump()

        return IRemUnknown2(
            INTERFACE(
                self.get_cinstance(),
                None,
                self.get_ipidRemUnknown(),
                resp["ppQIResults"]["std"]["ipid"],
                oxid=resp["ppQIResults"]["std"]["oxid"],
                oid=resp["ppQIResults"]["std"]["oxid"],
                target=self.get_target(),
            )
        )

    def RemAddRef(self):
        request = RemAddRef()
        request["ORPCthis"] = self.get_cinstance().get_ORPCthis()
        request["ORPCthis"]["flags"] = 0
        request["cInterfaceRefs"] = 1
        element = REMINTERFACEREF()
        element["ipid"] = self.get_iPid()
        element["cPublicRefs"] = 1
        request["InterfaceRefs"].append(element)
        resp = self.request(request, IID_IRemUnknown, self.get_ipidRemUnknown())
        return resp

    def RemRelease(self):
        request = RemRelease()
        request["ORPCthis"] = self.get_cinstance().get_ORPCthis()
        request["ORPCthis"]["flags"] = 0
        request["cInterfaceRefs"] = 1
        element = REMINTERFACEREF()
        element["ipid"] = self.get_iPid()
        element["cPublicRefs"] = 1
        request["InterfaceRefs"].append(element)
        resp = self.request(request, IID_IRemUnknown, self.get_ipidRemUnknown())
        DCOMConnection.delOid(self.get_target(), self.get_oid())
        return resp


# 3.1.1.5.7 IRemUnknown2 Interface
class IRemUnknown2(IRemUnknown):
    def __init__(self, interface):
        IRemUnknown.__init__(self, interface)
        self._iid = IID_IRemUnknown2


# 3.1.2.5.1 IObjectExporter Methods
class IObjectExporter:
    def __init__(self, dce):
        self.__portmap = dce

    # 3.1.2.5.1.1 IObjectExporter::ResolveOxid (Opnum 0)
    def ResolveOxid(self, pOxid, arRequestedProtseqs):
        self.__portmap.connect()
        self.__portmap.bind(IID_IObjectExporter)
        request = ResolveOxid()
        request["pOxid"] = pOxid
        request["cRequestedProtseqs"] = len(arRequestedProtseqs)
        for protSeq in arRequestedProtseqs:
            request["arRequestedProtseqs"].append(protSeq)
        resp = self.__portmap.request(request)
        Oxids = b"".join(pack("<H", x) for x in resp["ppdsaOxidBindings"]["aStringArray"])
        strBindings = Oxids[: resp["ppdsaOxidBindings"]["wSecurityOffset"] * 2]

        done = False
        stringBindings = list()
        while not done:
            if strBindings[0:1] == b"\x00" and strBindings[1:2] == b"\x00":
                done = True
            else:
                binding = STRINGBINDING(strBindings)
                stringBindings.append(binding)
                strBindings = strBindings[len(binding) :]

        return stringBindings

    # 3.1.2.5.1.2 IObjectExporter::SimplePing (Opnum 1)
    def SimplePing(self, setId):
        self.__portmap.connect()
        self.__portmap.bind(IID_IObjectExporter)
        request = SimplePing()
        request["pSetId"] = setId
        resp = self.__portmap.request(request)
        return resp

    # 3.1.2.5.1.3 IObjectExporter::ComplexPing (Opnum 2)
    def ComplexPing(self, setId=0, sequenceNum=0, addToSet=[], delFromSet=[]):
        self.__portmap.connect()
        # self.__portmap.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_INTEGRITY)
        self.__portmap.bind(IID_IObjectExporter)
        request = ComplexPing()
        request["pSetId"] = setId
        request["SequenceNum"] = setId
        request["cAddToSet"] = len(addToSet)
        request["cDelFromSet"] = len(delFromSet)
        if len(addToSet) > 0:
            for oid in addToSet:
                oidn = OID()
                oidn["Data"] = oid
                request["AddToSet"].append(oidn)
        else:
            request["AddToSet"] = NULL

        if len(delFromSet) > 0:
            for oid in delFromSet:
                oidn = OID()
                oidn["Data"] = oid
                request["DelFromSet"].append(oidn)
        else:
            request["DelFromSet"] = NULL
        resp = self.__portmap.request(request)
        return resp

    # 3.1.2.5.1.4 IObjectExporter::ServerAlive (Opnum 3)
    def ServerAlive(self):
        self.__portmap.connect()
        self.__portmap.bind(IID_IObjectExporter)
        request = ServerAlive()
        resp = self.__portmap.request(request)
        return resp

    # 3.1.2.5.1.5 IObjectExporter::ResolveOxid2 (Opnum 4)
    def ResolveOxid2(self, pOxid, arRequestedProtseqs):
        self.__portmap.connect()
        self.__portmap.bind(IID_IObjectExporter)
        request = ResolveOxid2()
        request["pOxid"] = pOxid
        request["cRequestedProtseqs"] = len(arRequestedProtseqs)
        for protSeq in arRequestedProtseqs:
            request["arRequestedProtseqs"].append(protSeq)
        resp = self.__portmap.request(request)
        Oxids = b"".join(pack("<H", x) for x in resp["ppdsaOxidBindings"]["aStringArray"])
        strBindings = Oxids[: resp["ppdsaOxidBindings"]["wSecurityOffset"] * 2]

        done = False
        stringBindings = list()
        while not done:
            if strBindings[0:1] == b"\x00" and strBindings[1:2] == b"\x00":
                done = True
            else:
                binding = STRINGBINDING(strBindings)
                stringBindings.append(binding)
                strBindings = strBindings[len(binding) :]

        return stringBindings

    # 3.1.2.5.1.6 IObjectExporter::ServerAlive2 (Opnum 5)
    def ServerAlive2(self):
        self.__portmap.connect()
        self.__portmap.bind(IID_IObjectExporter)
        request = ServerAlive2()
        resp = self.__portmap.request(request)

        Oxids = b"".join(pack("<H", x) for x in resp["ppdsaOrBindings"]["aStringArray"])
        strBindings = Oxids[: resp["ppdsaOrBindings"]["wSecurityOffset"] * 2]

        done = False
        stringBindings = list()
        while not done:
            if strBindings[0:1] == b"\x00" and strBindings[1:2] == b"\x00":
                done = True
            else:
                binding = STRINGBINDING(strBindings)
                stringBindings.append(binding)
                strBindings = strBindings[len(binding) :]

        return stringBindings


# 3.1.2.5.2.1 IActivation Methods
class IActivation:
    def __init__(self, dce):
        self.__portmap = dce

    # 3.1.2.5.2.3.1 IActivation:: RemoteActivation (Opnum 0)
    def RemoteActivation(self, clsId, iid):
        # Only supports one interface at a time
        self.__portmap.bind(IID_IActivation)
        ORPCthis = ORPCTHIS()
        ORPCthis["cid"] = generate()
        ORPCthis["extensions"] = NULL
        ORPCthis["flags"] = 1

        request = RemoteActivation()
        request["Clsid"] = clsId
        request["pwszObjectName"] = NULL
        request["pObjectStorage"] = NULL
        request["ClientImpLevel"] = 2
        request["Mode"] = 0
        request["Interfaces"] = 1

        _iid = IID()
        _iid["Data"] = iid

        request["pIIDs"].append(_iid)
        request["cRequestedProtseqs"] = 1
        request["aRequestedProtseqs"].append(7)

        resp = self.__portmap.request(request)

        # Now let's parse the answer and build an Interface instance

        ipidRemUnknown = resp["pipidRemUnknown"]

        Oxids = b"".join(pack("<H", x) for x in resp["ppdsaOxidBindings"]["aStringArray"])
        strBindings = Oxids[: resp["ppdsaOxidBindings"]["wSecurityOffset"] * 2]
        securityBindings = Oxids[resp["ppdsaOxidBindings"]["wSecurityOffset"] * 2 :]

        done = False
        stringBindings = list()
        while not done:
            if strBindings[0:1] == b"\x00" and strBindings[1:2] == b"\x00":
                done = True
            else:
                binding = STRINGBINDING(strBindings)
                stringBindings.append(binding)
                strBindings = strBindings[len(binding) :]

        done = False
        while not done:
            if len(securityBindings) < 2:
                done = True
            elif securityBindings[0:1] == b"\x00" and securityBindings[1:2] == b"\x00":
                done = True
            else:
                secBinding = SECURITYBINDING(securityBindings)
                securityBindings = securityBindings[len(secBinding) :]

        classInstance = CLASS_INSTANCE(ORPCthis, stringBindings)
        return IRemUnknown2(
            INTERFACE(
                classInstance,
                b"".join(resp["ppInterfaceData"][0]["abData"]),
                ipidRemUnknown,
                target=self.__portmap.get_rpc_transport().getRemoteHost(),
            )
        )


# 3.1.2.5.2.2 IRemoteSCMActivator Methods
class IRemoteSCMActivator:
    def __init__(self, dce):
        self.__portmap = dce

    def RemoteGetClassObject(self, clsId, iid):
        #  iid should be IID_IClassFactory
        self.__portmap.bind(IID_IRemoteSCMActivator)
        ORPCthis = ORPCTHIS()
        ORPCthis["cid"] = generate()
        ORPCthis["extensions"] = NULL
        ORPCthis["flags"] = 1

        request = RemoteGetClassObject()
        request["ORPCthis"] = ORPCthis
        activationBLOB = ACTIVATION_BLOB()
        activationBLOB["CustomHeader"]["destCtx"] = 2
        activationBLOB["CustomHeader"]["pdwReserved"] = NULL
        clsid = CLSID()
        clsid["Data"] = CLSID_InstantiationInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)
        clsid = CLSID()
        clsid["Data"] = CLSID_ActivationContextInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)
        clsid = CLSID()
        clsid["Data"] = CLSID_ServerLocationInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)
        clsid = CLSID()
        clsid["Data"] = CLSID_ScmRequestInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)

        properties = b""
        # InstantiationInfo
        instantiationInfo = InstantiationInfoData()
        instantiationInfo["classId"] = clsId
        instantiationInfo["cIID"] = 1

        _iid = IID()
        _iid["Data"] = iid

        instantiationInfo["pIID"].append(_iid)

        dword = DWORD()
        marshaled = instantiationInfo.getData() + instantiationInfo.getDataReferents()
        pad = (8 - (len(marshaled) % 8)) % 8
        dword["Data"] = len(marshaled) + pad
        activationBLOB["CustomHeader"]["pSizes"].append(dword)
        instantiationInfo["thisSize"] = dword["Data"]

        properties += marshaled + b"\xFA" * pad

        # ActivationContextInfoData
        activationInfo = ActivationContextInfoData()
        activationInfo["pIFDClientCtx"] = NULL
        activationInfo["pIFDPrototypeCtx"] = NULL

        dword = DWORD()
        marshaled = activationInfo.getData() + activationInfo.getDataReferents()
        pad = (8 - (len(marshaled) % 8)) % 8
        dword["Data"] = len(marshaled) + pad
        activationBLOB["CustomHeader"]["pSizes"].append(dword)

        properties += marshaled + b"\xFA" * pad

        # ServerLocation
        locationInfo = LocationInfoData()
        locationInfo["machineName"] = NULL

        dword = DWORD()
        dword["Data"] = len(locationInfo.getData())
        activationBLOB["CustomHeader"]["pSizes"].append(dword)

        properties += locationInfo.getData() + locationInfo.getDataReferents()

        # ScmRequestInfo
        scmInfo = ScmRequestInfoData()
        scmInfo["pdwReserved"] = NULL
        # scmInfo['remoteRequest']['ClientImpLevel'] = 2
        scmInfo["remoteRequest"]["cRequestedProtseqs"] = 1
        scmInfo["remoteRequest"]["pRequestedProtseqs"].append(7)

        dword = DWORD()
        marshaled = scmInfo.getData() + scmInfo.getDataReferents()
        pad = (8 - (len(marshaled) % 8)) % 8
        dword["Data"] = len(marshaled) + pad
        activationBLOB["CustomHeader"]["pSizes"].append(dword)

        properties += marshaled + b"\xFA" * pad

        activationBLOB["Property"] = properties

        objrefcustom = OBJREF_CUSTOM()
        objrefcustom["iid"] = IID_IActivationPropertiesIn[:-4]
        objrefcustom["clsid"] = CLSID_ActivationPropertiesIn

        objrefcustom["pObjectData"] = activationBLOB.getData()
        objrefcustom["ObjectReferenceSize"] = len(objrefcustom["pObjectData"]) + 8

        request["pActProperties"]["ulCntData"] = len(objrefcustom.getData())
        request["pActProperties"]["abData"] = list(objrefcustom.getData())
        resp = self.__portmap.request(request)
        # Now let's parse the answer and build an Interface instance

        objRefType = OBJREF(b"".join(resp["ppActProperties"]["abData"]))["flags"]
        objRef = None
        if objRefType == FLAGS_OBJREF_CUSTOM:
            objRef = OBJREF_CUSTOM(b"".join(resp["ppActProperties"]["abData"]))
        elif objRefType == FLAGS_OBJREF_HANDLER:
            objRef = OBJREF_HANDLER(b"".join(resp["ppActProperties"]["abData"]))
        elif objRefType == FLAGS_OBJREF_STANDARD:
            objRef = OBJREF_STANDARD(b"".join(resp["ppActProperties"]["abData"]))
        elif objRefType == FLAGS_OBJREF_EXTENDED:
            objRef = OBJREF_EXTENDED(b"".join(resp["ppActProperties"]["abData"]))
        else:
            LOG.error("Unknown OBJREF Type! 0x%x" % objRefType)

        activationBlob = ACTIVATION_BLOB(objRef["pObjectData"])

        propOutput = activationBlob["Property"][: activationBlob["CustomHeader"]["pSizes"][0]["Data"]]
        scmReply = activationBlob["Property"][
            activationBlob["CustomHeader"]["pSizes"][0]["Data"] : activationBlob["CustomHeader"]["pSizes"][0]["Data"]
            + activationBlob["CustomHeader"]["pSizes"][1]["Data"]
        ]

        scmr = ScmReplyInfoData()
        size = scmr.fromString(scmReply)
        # Processing the scmReply
        scmr.fromStringReferents(scmReply[size:])
        ipidRemUnknown = scmr["remoteReply"]["ipidRemUnknown"]
        Oxids = b"".join(pack("<H", x) for x in scmr["remoteReply"]["pdsaOxidBindings"]["aStringArray"])
        strBindings = Oxids[: scmr["remoteReply"]["pdsaOxidBindings"]["wSecurityOffset"] * 2]
        securityBindings = Oxids[scmr["remoteReply"]["pdsaOxidBindings"]["wSecurityOffset"] * 2 :]

        done = False
        stringBindings = list()
        while not done:
            if strBindings[0:1] == b"\x00" and strBindings[1:2] == b"\x00":
                done = True
            else:
                binding = STRINGBINDING(strBindings)
                stringBindings.append(binding)
                strBindings = strBindings[len(binding) :]

        done = False
        while not done:
            if len(securityBindings) < 2:
                done = True
            elif securityBindings[0:1] == b"\x00" and securityBindings[1:2] == b"\x00":
                done = True
            else:
                secBinding = SECURITYBINDING(securityBindings)
                securityBindings = securityBindings[len(secBinding) :]

        # Processing the Properties Output
        propsOut = PropsOutInfo()
        size = propsOut.fromString(propOutput)
        propsOut.fromStringReferents(propOutput[size:])

        classInstance = CLASS_INSTANCE(ORPCthis, stringBindings)
        classInstance.set_auth_level(scmr["remoteReply"]["authnHint"])
        classInstance.set_auth_type(self.__portmap.get_auth_type())
        return IRemUnknown2(
            INTERFACE(
                classInstance,
                b"".join(propsOut["ppIntfData"][0]["abData"]),
                ipidRemUnknown,
                target=self.__portmap.get_rpc_transport().getRemoteHost(),
            )
        )

    def RemoteCreateInstance(self, clsId, iid):
        # Only supports one interface at a time
        self.__portmap.bind(IID_IRemoteSCMActivator)

        ORPCthis = ORPCTHIS()
        ORPCthis["cid"] = generate()
        ORPCthis["extensions"] = NULL
        ORPCthis["flags"] = 1

        request = RemoteCreateInstance()
        request["ORPCthis"] = ORPCthis
        request["pUnkOuter"] = NULL

        activationBLOB = ACTIVATION_BLOB()
        activationBLOB["CustomHeader"]["destCtx"] = 2
        activationBLOB["CustomHeader"]["pdwReserved"] = NULL
        clsid = CLSID()
        clsid["Data"] = CLSID_InstantiationInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)
        clsid = CLSID()
        clsid["Data"] = CLSID_ActivationContextInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)
        clsid = CLSID()
        clsid["Data"] = CLSID_ServerLocationInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)
        clsid = CLSID()
        clsid["Data"] = CLSID_ScmRequestInfo
        activationBLOB["CustomHeader"]["pclsid"].append(clsid)

        properties = b""
        # InstantiationInfo
        instantiationInfo = InstantiationInfoData()
        instantiationInfo["classId"] = clsId
        instantiationInfo["cIID"] = 1

        _iid = IID()
        _iid["Data"] = iid

        instantiationInfo["pIID"].append(_iid)

        dword = DWORD()
        marshaled = instantiationInfo.getData() + instantiationInfo.getDataReferents()
        pad = (8 - (len(marshaled) % 8)) % 8
        dword["Data"] = len(marshaled) + pad
        activationBLOB["CustomHeader"]["pSizes"].append(dword)
        instantiationInfo["thisSize"] = dword["Data"]

        properties += marshaled + b"\xFA" * pad

        # ActivationContextInfoData
        activationInfo = ActivationContextInfoData()
        activationInfo["pIFDClientCtx"] = NULL
        activationInfo["pIFDPrototypeCtx"] = NULL

        dword = DWORD()
        marshaled = activationInfo.getData() + activationInfo.getDataReferents()
        pad = (8 - (len(marshaled) % 8)) % 8
        dword["Data"] = len(marshaled) + pad
        activationBLOB["CustomHeader"]["pSizes"].append(dword)

        properties += marshaled + b"\xFA" * pad

        # ServerLocation
        locationInfo = LocationInfoData()
        locationInfo["machineName"] = NULL

        dword = DWORD()
        dword["Data"] = len(locationInfo.getData())
        activationBLOB["CustomHeader"]["pSizes"].append(dword)

        properties += locationInfo.getData() + locationInfo.getDataReferents()

        # ScmRequestInfo
        scmInfo = ScmRequestInfoData()
        scmInfo["pdwReserved"] = NULL
        # scmInfo['remoteRequest']['ClientImpLevel'] = 2
        scmInfo["remoteRequest"]["cRequestedProtseqs"] = 1
        scmInfo["remoteRequest"]["pRequestedProtseqs"].append(7)

        dword = DWORD()
        marshaled = scmInfo.getData() + scmInfo.getDataReferents()
        pad = (8 - (len(marshaled) % 8)) % 8
        dword["Data"] = len(marshaled) + pad
        activationBLOB["CustomHeader"]["pSizes"].append(dword)

        properties += marshaled + b"\xFA" * pad

        activationBLOB["Property"] = properties

        objrefcustom = OBJREF_CUSTOM()
        objrefcustom["iid"] = IID_IActivationPropertiesIn[:-4]
        objrefcustom["clsid"] = CLSID_ActivationPropertiesIn

        objrefcustom["pObjectData"] = activationBLOB.getData()
        objrefcustom["ObjectReferenceSize"] = len(objrefcustom["pObjectData"]) + 8

        request["pActProperties"]["ulCntData"] = len(objrefcustom.getData())
        request["pActProperties"]["abData"] = list(objrefcustom.getData())
        resp = self.__portmap.request(request)

        # Now let's parse the answer and build an Interface instance

        objRefType = OBJREF(b"".join(resp["ppActProperties"]["abData"]))["flags"]
        objRef = None
        if objRefType == FLAGS_OBJREF_CUSTOM:
            objRef = OBJREF_CUSTOM(b"".join(resp["ppActProperties"]["abData"]))
        elif objRefType == FLAGS_OBJREF_HANDLER:
            objRef = OBJREF_HANDLER(b"".join(resp["ppActProperties"]["abData"]))
        elif objRefType == FLAGS_OBJREF_STANDARD:
            objRef = OBJREF_STANDARD(b"".join(resp["ppActProperties"]["abData"]))
        elif objRefType == FLAGS_OBJREF_EXTENDED:
            objRef = OBJREF_EXTENDED(b"".join(resp["ppActProperties"]["abData"]))
        else:
            LOG.error("Unknown OBJREF Type! 0x%x" % objRefType)

        activationBlob = ACTIVATION_BLOB(objRef["pObjectData"])

        propOutput = activationBlob["Property"][: activationBlob["CustomHeader"]["pSizes"][0]["Data"]]
        scmReply = activationBlob["Property"][
            activationBlob["CustomHeader"]["pSizes"][0]["Data"] : activationBlob["CustomHeader"]["pSizes"][0]["Data"]
            + activationBlob["CustomHeader"]["pSizes"][1]["Data"]
        ]

        scmr = ScmReplyInfoData()
        size = scmr.fromString(scmReply)
        # Processing the scmReply
        scmr.fromStringReferents(scmReply[size:])
        ipidRemUnknown = scmr["remoteReply"]["ipidRemUnknown"]
        Oxids = b"".join(pack("<H", x) for x in scmr["remoteReply"]["pdsaOxidBindings"]["aStringArray"])
        strBindings = Oxids[: scmr["remoteReply"]["pdsaOxidBindings"]["wSecurityOffset"] * 2]
        securityBindings = Oxids[scmr["remoteReply"]["pdsaOxidBindings"]["wSecurityOffset"] * 2 :]

        done = False
        stringBindings = list()
        while not done:
            if strBindings[0:1] == b"\x00" and strBindings[1:2] == b"\x00":
                done = True
            else:
                binding = STRINGBINDING(strBindings)
                stringBindings.append(binding)
                strBindings = strBindings[len(binding) :]

        done = False
        while not done:
            if len(securityBindings) < 2:
                done = True
            elif securityBindings[0:1] == b"\x00" and securityBindings[1:2] == b"\x00":
                done = True
            else:
                secBinding = SECURITYBINDING(securityBindings)
                securityBindings = securityBindings[len(secBinding) :]

        # Processing the Properties Output
        propsOut = PropsOutInfo()
        size = propsOut.fromString(propOutput)
        propsOut.fromStringReferents(propOutput[size:])

        classInstance = CLASS_INSTANCE(ORPCthis, stringBindings)
        classInstance.set_auth_level(scmr["remoteReply"]["authnHint"])
        classInstance.set_auth_type(self.__portmap.get_auth_type())
        return IRemUnknown2(
            INTERFACE(
                classInstance,
                b"".join(propsOut["ppIntfData"][0]["abData"]),
                ipidRemUnknown,
                target=self.__portmap.get_rpc_transport().getRemoteHost(),
            )
        )
